import React, {
  Fragment,
  ReactElement,
  useEffect,
  useRef,
  useState,
} from "react";
import { ResizeObserver } from "@juggle/resize-observer";

declare const version: string;
export interface SquareInfoInImage {
  L: number;
  R: number;
  T: number;
  B: number;
  OX: number;
  OY: number;
  SX: number;
  SY: number;
  F: number;
  V: number;
  v: string;
  w: number;
  h: number;
  c: string;
  p: number;
}

export interface SquareInfo {
  width: number;
  height: number;
  left: number;
  top: number;
  right: number;
  bottom: number;
  offsetX: number;
  offsetY: number;
  scaleX: number;
  scaleY: number;
  fontSize: number;
  vertical: boolean;
  color: string;
  pixelRatio: number;
}
let pixel: any;
const testWord = "这是一条测试文本";
try {
  //@ts-ignore
  pixel = require("@ke/ke-pixel/item");
} catch (e) {
  console.log("没有找到ke-pixel");
}

export const PreviewerCanvas = (
  props: SquareInfo & {
    text?: string;
    render?: ReactElement;
    autoFixed?: boolean;
    checkEachFrame?: boolean;
    src: string;
  }
) => {
  const coverRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const textRef = useRef<HTMLDivElement>(null);
  const imageRef = useRef({ img: new Image(), src: "", loaded: false });
  const [renderCounter, setRenderCounter] = useState(0);
  //prettier-ignore
  let {
    width,        height,         left,          top,                right,            bottom,
    offsetX,      offsetY,        scaleX,        scaleY,             fontSize,         vertical,
    text,         src,            color,         pixelRatio:picX,    render,
  } = props;

  right = width - right;
  bottom = height - bottom;
  const ratio = fontSize / 12;

  function waitForAppear() {
    return new Promise<void>((resolve) => {
      if (textRef.current?.offsetParent === null) {
        requestAnimationFrame(() => {
          waitForAppear()?.then(resolve);
        });
      } else {
        resolve();
      }
    });
  }

  useEffect(() => {
    let observer: ResizeObserver | undefined;
    if (textRef.current && props.render && props.autoFixed) {
      observer = new ResizeObserver(() => {
        setRenderCounter((counter) => counter + 1);
      });
      observer.observe(textRef.current);
    }
    return () => {
      if (observer) {
        observer.disconnect();
      }
    };
  }, [props.autoFixed]);

  useEffect(() => {
    let timer: any;
    let keepCheck = props.checkEachFrame;
    const checker = () => {
      if (keepCheck) {
        timer = requestAnimationFrame(checker);
      }
      setRenderCounter((counter) => counter + 1);
    };
    if (props.checkEachFrame) {
      checker();
    }
    return () => {
      cancelAnimationFrame(timer);
      keepCheck = false;
    };
  }, [props.checkEachFrame]);

  useEffect(
    () => {
      let retryTime = 0;
      const refreshCanvas = () => {
        if (!src) return;
        const canvas = canvasRef.current as HTMLCanvasElement;
        const ctx = canvas?.getContext("2d") as CanvasRenderingContext2D;
        if (!canvas || !ctx) {
          return;
        }
        let textWidth = 0;
        let textHeight = 0;
        let iText: any;
        const pixelRatio = window.devicePixelRatio;
        if (!pixel || props.render !== undefined) {
          //第一步（新）读取文本宽高
          if (!textRef.current) {
            if (retryTime < 100) {
              requestAnimationFrame(refreshCanvas);
              retryTime++;
            }
            return;
          }

          const box = textRef.current.getBoundingClientRect();
          textWidth = box.width;
          textHeight = box.height;
          if (textWidth === 0) {
            if (retryTime < 100) {
              setTimeout(refreshCanvas, 16 * 5);
              retryTime++;
            }
            return;
          }
        } else {
          const { TextItem } = pixel;
          iText = new TextItem({
            fill: color,
            vertical: vertical,
            value: text || testWord,
            fontSize: fontSize * picX,
            lineHeight: fontSize * picX * (vertical ? 1.3 : 1.5715),
            textBaseline: "top",
          });
          const boundingBox = iText.getBoundingBox();

          const disLeft = left / picX,
            disTop = top / picX;
          iText.x = (disLeft + offsetX) * picX;
          iText.y = (disTop + offsetY) * picX;

          const bodyWidth = Math.ceil(boundingBox.width / picX);
          const bodyHeight = Math.ceil(boundingBox.height / picX);
          textWidth = bodyWidth;
          textHeight = bodyHeight;
        }

        //第二步：读取底图
        const imageSource = imageRef.current;
        if (imageSource.src === src) {
          if (imageSource.loaded) {
            drawBorder();
          } else {
            imageSource.img.onload = drawBorder;
          }
        } else {
          imageSource.src = src;
          imageSource.img.src = src;
          imageSource.img.onload = drawBorder;
        }

        //第三步：绘制边框
        function drawBorder() {
          imageSource.loaded = true;
          const image = imageSource.img;
          image.onload = null;
          const disLeft = left / picX,
            disRight = right / picX,
            disTop = top / picX,
            disBottom = bottom / picX;
          const totalWidth = Math.ceil(textWidth + disLeft + disRight - scaleX);
          const totalHeight = Math.ceil(
            textHeight + disTop + disBottom - scaleY
          );
          //1.设置canvas宽高
          canvas.width = totalWidth * pixelRatio;
          canvas.height = totalHeight * pixelRatio;

          draw9PngToCanvas(ctx, image, {
            target: {
              x: 0,
              y: 0,
              width: totalWidth,
              height: totalHeight,
              pixelRatio,
            },
            source: {
              pixelRatio: picX,
              left,
              top,
              right,
              bottom,
              width,
              height,
            },
          });

          if (iText) {
            iText.setRenderStyle(ctx);
            iText.render(
              ctx,
              (x: number, y: number) => [x / picX, y / picX],
              canvas
            );
          }

          //最后一步：设置背景
          canvas.style.cssText = [
            `width:${totalWidth}px`,
            `height:${totalHeight}px`,
          ].join(";");
        }
      };
      waitForAppear().then(() => {
        refreshCanvas();
      });
    },
    //prettier-ignore
    [
      width,    height,    left,    top,     right,     bottom,
      offsetX,  offsetY,   scaleX,  scaleY,  fontSize,  vertical,
      text,     src,       picX,    render,  renderCounter
    ]
  );

  return (
    <div ref={coverRef} style={{ position: "relative" }}>
      <canvas width="1" height="1" ref={canvasRef}></canvas>
      <div
        ref={textRef}
        style={{
          position: "absolute",
          top: top / picX + offsetY,
          left: left / picX + offsetX,
          whiteSpace: "nowrap",
          fontSize: 12,
          transformOrigin: "left top",
          transform: `scale(${ratio})`,
          writingMode: vertical ? "vertical-lr" : "horizontal-tb",
          textOrientation: vertical ? "upright" : "sideways",
          textAlign: "center",
          color: color,
          lineHeight: 1.5715,
        }}
      >
        {(() => {
          if (!pixel || props.render !== undefined)
            return (
              <>
                {props.render
                  ? props.render
                  : props.text
                  ? props.text.split("\n").map((item, index, all) => {
                      return (
                        <Fragment key={index}>
                          <span>{item}</span>
                          {all.length !== index + 1 && <br />}
                        </Fragment>
                      );
                    })
                  : testWord}
              </>
            );
          return null;
        })()}
      </div>
    </div>
  );
};

export default PreviewerCanvas;

export function draw9PngToCanvas(
  ctx: CanvasRenderingContext2D,
  image: HTMLImageElement,
  options: {
    source: {
      left: number;
      top: number;
      right: number;
      bottom: number;
      width: number;
      height: number;
      pixelRatio: number;
    };
    target: {
      x?: number;
      y?: number;
      width: number;
      height: number;
      pixelRatio?: number;
    };
  }
) {
  const { pixelRatio, left, top, right, bottom, width, height } =
    options.source;
  const {
    x = 0,
    y = 0,
    width: totalWidth,
    height: totalHeight,
    pixelRatio: targetPixelRatio = window.devicePixelRatio,
  } = options.target;
  const disLeft = Math.floor(left / pixelRatio),
    disRight = Math.ceil(right / pixelRatio),
    disTop = Math.floor(top / pixelRatio),
    disBottom = Math.ceil(bottom / pixelRatio);

  const bodyWidth = Math.max(0, totalWidth - disLeft - disRight);
  const bodyHeight = Math.max(0, totalHeight - disTop - disBottom);
  const maskWidth = Math.max(0, width - left - right);
  const maskHeight = Math.max(0, height - bottom - top);
  //2.计算本体最终属性
  const widthRate = (totalWidth - bodyWidth) / (disLeft + disRight);
  let bodyLeft = disLeft,
    bodyRight = disRight,
    bodyTop = disTop,
    bodyBottom = disBottom;
  if (widthRate < 1) {
    bodyLeft = Math.floor(bodyLeft * widthRate);
    bodyRight = Math.ceil(bodyRight * widthRate);
  }
  const heightRate = (totalHeight - bodyHeight) / (disTop + disBottom);
  if (heightRate < 1) {
    bodyTop = Math.floor(bodyTop * heightRate);
    bodyBottom = Math.ceil(bodyBottom * heightRate);
  }
  //绘制本体
  ctx.drawImage(
    image,
    left,
    top,
    maskWidth,
    maskHeight,
    x + bodyLeft * targetPixelRatio,
    y + bodyTop * targetPixelRatio,
    bodyWidth * targetPixelRatio,
    bodyHeight * targetPixelRatio
  );

  //绘制↖️边框
  ctx.drawImage(
    image,
    0,
    0,
    left,
    top,
    x,
    y,
    bodyLeft * targetPixelRatio,
    bodyTop * targetPixelRatio
  );

  //绘制⬅️边框
  ctx.drawImage(
    image,
    0,
    top,
    left,
    maskHeight,
    x,
    y + bodyTop * targetPixelRatio,
    bodyLeft * targetPixelRatio,
    bodyHeight * targetPixelRatio
  );

  //绘制⬆️边框
  ctx.drawImage(
    image,
    left,
    0,
    maskWidth,
    top,
    x + bodyLeft * targetPixelRatio,
    y,
    bodyWidth * targetPixelRatio,
    bodyTop * targetPixelRatio
  );

  //绘制↗️边框
  ctx.drawImage(
    image,
    left + maskWidth,
    0,
    right,
    top,
    x + (bodyLeft + bodyWidth) * targetPixelRatio,
    y,
    bodyRight * targetPixelRatio,
    bodyTop * targetPixelRatio
  );

  //绘制➡️边框
  ctx.drawImage(
    image,
    left + maskWidth,
    top,
    right,
    maskHeight,
    x + (bodyLeft + bodyWidth) * targetPixelRatio,
    y + bodyTop * targetPixelRatio,
    bodyRight * targetPixelRatio,
    bodyHeight * targetPixelRatio
  );

  //绘制↙️边框
  ctx.drawImage(
    image,
    0,
    top + maskHeight,
    left,
    bottom,
    x,
    y + (bodyTop + bodyHeight) * targetPixelRatio,
    bodyLeft * targetPixelRatio,
    bodyBottom * targetPixelRatio
  );

  //绘制⬇️边框
  ctx.drawImage(
    image,
    left,
    top + maskHeight,
    maskWidth,
    bottom,
    x + bodyLeft * targetPixelRatio,
    y + (bodyTop + bodyHeight) * targetPixelRatio,
    bodyWidth * targetPixelRatio,
    bodyBottom * targetPixelRatio
  );

  //绘制↘️边框
  ctx.drawImage(
    image,
    left + maskWidth,
    top + maskHeight,
    right,
    bottom,
    x + (bodyLeft + bodyWidth) * targetPixelRatio,
    y + (bodyTop + bodyHeight) * targetPixelRatio,
    bodyRight * targetPixelRatio,
    bodyBottom * targetPixelRatio
  );
}

class Cache {
  static map = new Map<string, Promise<SquareInfoInImage>>();
  static getImageSource(src: string): SquareDataOrigin {
    const cache = Cache.map.get(src);
    if (cache) {
      return cache;
    } else {
      const promise = new Promise<SquareInfoInImage>((resolve) => {
        new Promise<ArrayBuffer | undefined>((resolve) => {
          fetch(src)
            .then((res) => res.arrayBuffer())
            .then((buffer) => {
              resolve(buffer);
            })
            .catch(() => {
              resolve(undefined);
            });
        })
          .then((buffer) => {
            if (buffer) {
              const array = new Uint8Array(buffer);
              const view = new DataView(buffer);
              const dataArray: number[] = [];
              let pointer = 0;
              let endPoint = 0;
              let width = 0;
              let height = 0;
              for (; endPoint < 1000 && endPoint < array.length; endPoint++) {
                const p1 = array[endPoint];
                const p2 = array[endPoint + 1];
                const p3 = array[endPoint + 2];
                const p4 = array[endPoint + 3];
                //监测到IDAT 说明头部信息已读取完成
                if (p1 === 0x49 && p2 === 0x44 && p3 === 0x41 && p4 === 0x54)
                  break;
                //监测到IHDR 读取长宽
                if (p1 === 0x49 && p2 === 0x48 && p3 === 0x44 && p4 === 0x52) {
                  width = view.getUint32(endPoint + 4);
                  height = view.getUint32(endPoint + 8);
                }
                //监测到tEXt 开始截取头部文本内容
                if (p1 === 0x74 && p2 === 0x45 && p3 === 0x58 && p4 === 0x74) {
                  pointer = endPoint;
                }
                if (pointer > 0) dataArray.push(p1);
              }
              if (pointer > 0 && endPoint > 0) {
                const data = dataArray.reduce((result, item) => {
                  return result + String.fromCharCode(item);
                }, "");
                const match = data.match(/9PS.+9PE/);
                if (match?.[0]) {
                  try {
                    const props = JSON.parse(match[0].replace(/9P(S|E)/g, ""));
                    if (props.v) {
                      console.log(
                        "当前解析器版本:",
                        version,
                        "\n解析到九宫格信息,版本:",
                        props.v
                      );
                      return { ...props, w: width, h: height };
                    }
                  } catch (e) {
                    console.warn("未解析到九宫格信息");
                  }
                }
              }
            }
          })
          .then((result) => {
            resolve(result);
          });
      });
      Cache.map.set(src, promise);
      return promise;
    }
  }
}

type SquareDataOrigin = Promise<SquareInfoInImage | undefined>;
export function SquareInfoExtractor(sourceUrl: string): SquareDataOrigin {
  return Cache.getImageSource(sourceUrl);
}

export function SquareInfoAdapter(originData: SquareDataOrigin) {
  return new Promise<SquareInfo>((resolve, reject) => {
    originData
      .then((sourceProps) => {
        if (sourceProps) {
          const data = {
            left: sourceProps.L,
            right: sourceProps.w - sourceProps.R,
            top: sourceProps.T,
            bottom: sourceProps.h - sourceProps.B,
            offsetX: sourceProps.OX,
            offsetY: sourceProps.OY,
            scaleX: sourceProps.SX,
            scaleY: sourceProps.SY,
            fontSize: sourceProps.F,
            width: sourceProps.w,
            height: sourceProps.h,
            vertical: Boolean(sourceProps.V),

            color: "#" + (sourceProps.c || "000"),
            pixelRatio: sourceProps.p || 1,
          };
          resolve(data);
        } else {
          reject("未解析到信息");
        }
      })
      .catch(() => {
        reject("信息解析失败");
      });
  });
}

// const ns = "http://www.w3.org/2000/svg";
// const svg = document.createElementNS(ns, "svg");
// svg.style.cssText = "position:absolute;left:-500px;top:-500px";
// const text = document.createElementNS(ns, "text");
// text.style.left = "0";
// text.style.top = "0";
// svg.appendChild(text);
// document.documentElement.appendChild(svg);
// export function checkTextBox(str: string, size = 11, vertical = false) {
//   text.innerHTML = str;
//   text.style.writingMode = vertical ? "tb" : "lr";
//   // text.setAttribute("rotate", vertical ? "-90" : "0");
//   text.style.fontSize = size + "px";
//   text.style.fontFamily = window.getComputedStyle(document.body).fontFamily;
//   let { width, height } = text.getBBox();
//   if (height === 0) width = 0;
//   return { width, height };
// }
